package com.derbysoft.lowcostadapter.schedule.net;

import com.derbysoft.lowcostadapter.dswitch2.service.helper.ProfileUtils;
import com.derbysoft.lowcostadapter.schedule.sync.impl.SyncFile;
import org.apache.commons.lang.Validate;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.List;

/**
 * User: jason
 * Date: 2012-11-01
 */
public abstract class FTPUtils {
    private static Logger logger = LoggerFactory.getLogger(FTPUtils.class);

    public static final String TMP = ".tmp";

    private static final String LOCAL_FOLDER = ProfileUtils.getUserFolder();

    public static void downloadFile(SyncFile syncFile, FTPClient ftpClient, FTPFile ftpFile) throws IOException {
        logger.info("Begin download [{}] file", syncFile);
        int retry = 0;
        while (true) {
            if (download(syncFile, ftpClient, ftpFile)) {
                break;
            }
            if (retry >= 2) {
                logger.error("Retry failed {} download [{}]", retry, syncFile);
                break;
            }
            retry++;
            logger.warn("Retry {} download [{}]", retry, syncFile);
        }
        logger.info("End download [{}]", syncFile);
    }

    public static boolean checkSyncFiles(List<SyncFile> syncFiles) throws IOException {
        boolean fileChanged = false;
        for (SyncFile syncFile : syncFiles) {
            if (checkSyncFile(syncFile)) {
                fileChanged = true;
            }
        }
        logger.info("fileChanged : " + fileChanged);
        return fileChanged;
    }

    private static boolean checkSyncFile(SyncFile syncFile) throws IOException {
        validateParameters(syncFile);

        return fileHasChange(syncFile);
    }

    private static boolean fileHasChange(SyncFile syncFile) throws IOException {

        boolean isFileChanged = false;

        FTPClient ftpClient = createFTPClient(syncFile);

        FTPFile ftpFile = getRemoteFileInfo(ftpClient, syncFile);

        if (fileHasChange(ftpFile, syncFile.getName())) {
            logger.info("========================------------> The [{}] file has been changed", syncFile.getName());

            isFileChanged = true;

            downloadFile(syncFile, ftpClient, ftpFile);

        } else {
            logger.info("The [{}] file has not changed", syncFile.getName());
        }

        ftpClient.disconnect();

        return isFileChanged;
    }

    private static boolean fileHasChange(FTPFile ftpFile, String fileName) {
        File localFile = new File(getLocalFilePathName(fileName));
        return !localFile.exists()
                || localFile.lastModified() != ftpFile.getTimestamp().getTimeInMillis()
                || localFile.length() != ftpFile.getSize();
    }

    private static void validateParameters(SyncFile syncFile) {
        Validate.notEmpty(syncFile.getFtpHost(), "Invalid ftp host");
        Validate.isTrue(syncFile.getFtpPort() != 0, "Invalid ftp port");
        Validate.notEmpty(syncFile.getFtpUserName(), "Invalid ftp user name");
        Validate.notEmpty(syncFile.getFtpPassword(), "Invalid ftp password");
        Validate.notEmpty(syncFile.getName(), "Invalid file name");
    }

    private static boolean download(SyncFile syncFile, FTPClient ftpClient, FTPFile ftpFile) {
        ensureFolderExist();

        String tempFileName = syncFile.getName() + TMP;

        File tmpFile = new File(getLocalFilePathName(tempFileName));
        FileOutputStream os = null;
        try {
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            os = new FileOutputStream(tmpFile);
            ftpClient.retrieveFile(getRemoteFilePathName(syncFile), os);
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    StringWriter writer = new StringWriter();
                    e.printStackTrace(new PrintWriter(writer));
                    logger.error(writer.toString());
                }
            }
        }
        return renameFile(tmpFile, syncFile.getName(), ftpFile.getTimestamp().getTimeInMillis());
    }

    private static boolean renameFile(File fileFrom, String to, long lastModify) {
        File fileTo = new File(getLocalFilePathName(to));
        if (fileFrom.exists()) {
            if (fileTo.exists()) {
                Validate.isTrue(fileTo.delete());
            }
            Validate.isTrue(fileFrom.renameTo(new File(getLocalFilePathName(to))));
            Validate.isTrue(fileTo.setLastModified(lastModify));
            return true;
        }
        return false;
    }

    private static void ensureFolderExist() {
        File folder = new File(LOCAL_FOLDER);
        if (!folder.exists()) {
            Validate.isTrue(folder.mkdirs());
        }
    }

    public static String getLocalFilePathName(String fileName) {
        return LOCAL_FOLDER + "/" + fileName;
    }

    private static String getRemoteFilePathName(SyncFile syncFile) {
        return syncFile.getFtpRemotePath() + "/" + syncFile.getName();
    }

    private static FTPFile getRemoteFileInfo(FTPClient ftpClient, SyncFile syncFile) throws IOException {
        FTPFile[] ftpFiles = ftpClient.listFiles(getRemoteFilePathName(syncFile));
        if (ftpFiles.length == 1) {
            return ftpFiles[0];
        }
        throw new FileNotFoundException(String.format("ftp://%s:%s:%s", syncFile.getFtpHost(), syncFile.getFtpHost(), syncFile.getName()));
    }

    private static FTPClient createFTPClient(SyncFile syncFile) throws IOException {
        FTPClient ftpClient = new FTPClient();
        ftpClient.connect(syncFile.getFtpHost(), syncFile.getFtpPort());
        ftpClient.login(syncFile.getFtpUserName(), syncFile.getFtpPassword());
        ftpClient.enterLocalPassiveMode();
        return ftpClient;
    }
}
